﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Threading;

namespace Autorun
{
	public abstract class TaskBase
	{
		#region "Private members"
		private Thread sysThread;
		private TaskStatuses sysStatus;
		private bool sysStopSignal;

		private TaskContext _context;
		private StringDictionary _parameters;
		private int _exitCode;
		#endregion

		#region "Events"
		internal event StartEventHandler OnStart;
		internal event StopEventHandler OnStop;
		internal event NotifyEventHandler OnNotify;
		#endregion

		#region "Constructors"
		protected TaskBase()
		{
			// === Create a new thread =============================================
			sysThread = new Thread(DoTask);

			// === Manage the parameters list ======================================
			_parameters = new StringDictionary();
			InitializeParameterList();

			// === initialize priate members =======================================
			sysStatus = TaskStatuses.NotStarted;
			sysStopSignal = false;
			_context = null;
		}
		#endregion

		#region "Properties"
		public StringDictionary Parameters
		{
			get { return _parameters; }
		}

		protected int ExitCode
		{
			get { return _exitCode; }
			set { _exitCode = value; }
		}

		protected internal TaskContext Context
		{
			get { return _context; }
		}

		internal TaskStatuses Status
		{
			get { return sysStatus; }
		}
		#endregion

		#region "Procedures"
		/// <summary>
		/// This function will help developers to test their task without using
		/// Autorun client.
		/// </summary>
		/// <param name="parameters"></param>
		public void Test()
		{
			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			PreProcess();
			Process();
			PostProcess();

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================

		}
		internal void Start(TaskContext context)
		{
			// If no context was passed, we create one because it gives feedback
			// to the user
			if (context == null)
				//context = new TaskContext();
				throw new Exception("No context was passed to the task");

			if (sysThread != null)
				switch (sysStatus)
				{
					case TaskStatuses.NotStarted:
					case TaskStatuses.Cancelled:
					case TaskStatuses.Finished:
					case TaskStatuses.Stopped:
						// === Start the task ============================================
						// --- Initialize the context ------------------------------------
						_context = context;
						_context.SetStartInformation();
						if (_context.Parameters != null)
							foreach (DictionaryEntry item in _context.Parameters)
								if (_parameters.ContainsKey(item.Key.ToString()) == true)
									_parameters[item.Key.ToString()] = item.Value.ToString();

						// --- Start the thread ------------------------------------------
						sysStopSignal = false; // Reset any previous stop signal
						sysThread.Start();
						break;
					case TaskStatuses.PostProcess:
					case TaskStatuses.PreProcess:
					case TaskStatuses.Process:
						throw new Exception(string.Format("The thread {0} is already running (Current status: {1}", sysThread.Name, sysStatus.ToString()));
					default:
						throw new Exception(string.Format("The thread {0} has an unmanaged status: {1}", sysThread.Name, sysStatus.ToString()));
				}
			else
				throw new Exception(string.Format("No thread created for the task {0}", this.ToString()));
		}

		internal void Stop()
		{
			sysStopSignal = true;
			sysThread.Join(1000);
			if ((sysStatus == TaskStatuses.PreProcess) ||
				(sysStatus == TaskStatuses.Process) ||
				(sysStatus == TaskStatuses.PostProcess))
			{
				sysThread.Abort();
				sysStatus = TaskStatuses.Stopped;
				_context.SetStopInformation(int.MinValue, new Exception("Task stopped after a stop signal has been sent from the server"));
				OnStop(this, new StopEventArgs(_context.ExitCode, _context.Error));
			}
		}

		private void DoTask()
		{
			// === Give the thread a new ===========================================
			sysThread.Name = string.Format("{0} - {1}", _context.Key, this.ToString());

			// === Core task process ===============================================
			if ((sysStatus == TaskStatuses.PreProcess) ||
				(sysStatus == TaskStatuses.Process) ||
				(sysStatus == TaskStatuses.PostProcess))
				throw new Exception(string.Format("The thread {0} cannot start since it is already started (Status: {1})", sysThread.Name, sysStatus.ToString()));
			else
			{
				// ===================================================================
				// === Pre-Process                                                 ===
				// ===================================================================
				sysStatus = TaskStatuses.NotStarted;

				if (OnStart != null) OnStart(this, new StartEventArgs());

				// ===================================================================
				// === Process                                                     ===
				// ===================================================================
				try
				{
					// --- Invoke the preprocessing step -------------------------------
					sysStatus = TaskStatuses.PreProcess;
					PreProcess();

					// --- Invoke the main processing step -----------------------------
					if (sysStopSignal == false)
					{
						sysStatus = TaskStatuses.Process;
						Process();
					}

					if (sysStopSignal == false)
					{
						sysStatus = TaskStatuses.PostProcess;
						PostProcess();
					}

					sysStatus = TaskStatuses.Finished;
				}
				catch (Exception ex)
				{
					sysStatus = TaskStatuses.Cancelled;
					_context.SetStopInformation(int.MinValue, ex);
					if (OnStop != null) OnStop(this, new StopEventArgs(_context.ExitCode, _context.Error));
				}

				// ===================================================================
				// === Post-Process                                                ===
				// ===================================================================
				// === The task has been successfully executed =======================
				if (sysStatus == TaskStatuses.Finished)
				{
					_context.SetStopInformation(_exitCode, null);
					// Notify only when normally finished
					// - When stop is forced, event is raised in Stop method
					// - In case of error, event is raised in the catch bloc
					if (OnStop != null) OnStop(this, new StopEventArgs(_context.ExitCode, _context.Error));
				}
			}
		}

		protected internal void Notify(SeverityLevels severity, string message)
		{
			if (OnNotify != null) OnNotify(this, new NotifyEventArgs(severity, message));
		}

		protected internal void ChainTask(string assemblyName, string className)
		{
			ChainTask(null, assemblyName, className, null);
		}

		protected internal void ChainTask(string assemblyName, string className, StringDictionary chainedParameters)
		{
			ChainTask(null, assemblyName, className, chainedParameters);
		}

		protected internal void ChainTask(string assemblyPath, string assemblyName, string className)
		{
			ChainTask(assemblyPath, assemblyName, className, null);
		}

		protected internal void ChainTask(string assemblyPath, string assemblyName, string className, StringDictionary chainedParameters)
		{
			string query;
			DataTable dt;

			// === Verify that the chained task is registered ======================
			query = string.Format("SELECT * FROM tblTask WHERE tskAssembly={0} AND tskClass={1} {2}",
				SqlConvert.SqlString(assemblyName),
				SqlConvert.SqlString(className),
				((assemblyPath != null) && (assemblyPath.Length != 0) ? string.Format(" AND tskPath={0}", SqlConvert.SqlString(assemblyPath)) : null));
			dt = ExecuteDataTable(query);

			switch (dt.Rows.Count)
			{
				case 0:
					// --- The task is not registered ----------------------------------
					throw new Exception(string.Format("Task not found (filename: \"{0}\", assembly name: \"{1}\", class name: \"{2}\")", assemblyPath, assemblyName, className));
				case 1:
					// --- The task is registered --------------------------------------
					_context.NextTaskId = (int)dt.Rows[0]["tskId"];
					_context.ChainedParameters = chainedParameters;
					break;
				default:
					// --- Too many task definitions have been found -------------------
					// This usually occurs when you pass only the assembly path and name, 
					// and the class name
					throw new Exception(string.Format("Several tasks have been found (filename: \"{0}\", assembly name: \"{1}\", class name: \"{2}\")", assemblyPath, assemblyName, className));
			}
		}

		protected abstract void PreProcess();
		protected abstract void Process();
		protected abstract void PostProcess();
		protected abstract void InitializeParameterList();
		#endregion

		#region "Data Access Methods"
		private void ExecuteNonQuery(string query)
		{
			SqlConnection cn;
			SqlCommand cmd;

			cn = new SqlConnection(Properties.Settings.Default.ConnectionString);

			try
			{
				cn.Open();
				cmd = cn.CreateCommand();
				cmd.CommandText = query;
				cmd.ExecuteNonQuery();
				cmd.Connection = null;
				cmd = null;
			}
			finally
			{
				cn.Close();
			}
		}

		private object ExecuteScalar(string query)
		{
			SqlConnection cn;
			SqlCommand cmd;
			object rc = null;

			cn = new SqlConnection(Properties.Settings.Default.ConnectionString);

			try
			{
				cn.Open();
				cmd = cn.CreateCommand();
				cmd.CommandText = query;
				rc = cmd.ExecuteScalar();
				cmd.Connection = null;
				cmd = null;
			}
			finally
			{
				cn.Close();
			}

			return rc;
		}

		private DataTable ExecuteDataTable(string query)
		{
			SqlDataAdapter da;
			DataSet rc;

			da = new SqlDataAdapter(query, Properties.Settings.Default.ConnectionString);
			rc = new DataSet();
			da.Fill(rc);
			return rc.Tables.Count != 0 ? rc.Tables[0] : null;
		}
		#endregion
	}
}
